﻿using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;

namespace LogoFX.Controls
{
    /// <summary>
    /// Represents a control that contains a collection of items and a content.
    /// </summary>
    /// <QualityBand>Stable</QualityBand>
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ContentPresenter))]
    [ContentProperty("Content")]
    public class ContentItemsControl : ItemsControl
    {
        #region Fields

        private static readonly Type s_thisType = typeof(ContentItemsControl);

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ContentItemsControl" /> class.
        /// </summary>
        public ContentItemsControl()
        {
            DefaultStyleKey = s_thisType;
            ItemsControlHelper = new ItemsControlHelper(this);
        }

        #endregion

        #region Dependency Properties

        /// <summary>
        /// Identifies the <see cref="Content" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="Content" /> dependency property.
        /// </value>
        /// <remarks>
        /// Note: WPF defines this property via a call to AddOwner of
        /// ContentedContentControl's ContentProperty.
        /// </remarks>
        public static readonly DependencyProperty ContentProperty =
            DependencyProperty.Register(
                "Content",
                typeof(object),
                s_thisType,
                new PropertyMetadata(OnContentPropertyChanged));

        /// <summary>
        /// Identifies the <see cref="ContentTemplate" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="ContentTemplate" /> dependency property.
        /// </value>
        /// <remarks>
        /// Note: WPF defines this property via a call to AddOwner of
        /// ContentedContentControl's ContentTemplateProperty.
        /// </remarks>
        public static readonly DependencyProperty ContentTemplateProperty =
            DependencyProperty.Register(
                "ContentTemplate",
                typeof(DataTemplate),
                s_thisType,
                new PropertyMetadata(OnContentTemplatePropertyChanged));

        /// <summary>
        /// Identifies the <see cref="ItemContainerStyle" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="ItemContainerStyle" /> dependency property.
        /// </value>
        public static readonly DependencyProperty ItemContainerStyleProperty =
            DependencyProperty.Register(
                "ItemContainerStyle",
                typeof(Style),
                s_thisType,
                new PropertyMetadata(null, OnItemContainerStylePropertyChanged));

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the item that labels the control.
        /// </summary>
        /// <value>
        /// The item that labels the control. The default value is null.
        /// </value>
        public object Content
        {
            get { return GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        /// <summary>
        /// Gets or sets a data template that is used to display the contents of
        /// the control's Content.
        /// </summary>
        /// <value>
        /// Gets or sets a data template that is used to display the contents of
        /// the control's Content. The default is null.
        /// </value>
        public DataTemplate ContentTemplate
        {
            get { return GetValue(ContentTemplateProperty) as DataTemplate; }
            set { SetValue(ContentTemplateProperty, value); }
        }

        /// <summary>
        /// Gets or sets the <see cref="Style" /> that is
        /// applied to the container element generated for each item.
        /// </summary>
        /// <value>
        /// The <see cref="Style" /> that is applied to the
        /// container element generated for each item. The default is null.
        /// </value>
        public Style ItemContainerStyle
        {
            get { return GetValue(ItemContainerStyleProperty) as Style; }
            set { SetValue(ItemContainerStyleProperty, value); }
        }

        #endregion

        #region Internal Members

        /// <summary>
        /// Gets or sets a value indicating whether the <see cref="Content"/> property has been
        /// set to the item of an <see cref="ItemsControl"/>.
        /// </summary>
        internal bool ContentIsItem { get; set; }

        /// <summary>
        /// Gets the <see cref="ItemsControlHelper"/> that is associated with this control.
        /// </summary>
        internal ItemsControlHelper ItemsControlHelper { get; private set; }

        /// <summary>
        /// Prepares the specified container to display the specified item.
        /// </summary>
        /// <param name="element">
        /// Container element used to display the specified item.
        /// </param>
        /// <param name="item">Specified item to display.</param>
        /// <param name="parent">The parent ItemsControl.</param>
        /// <param name="parentItemContainerStyle">
        /// The ItemContainerStyle for the parent ItemsControl.
        /// </param>
        internal static void PreparePrepareContentItemsControlContainerForItemOverride(DependencyObject element, object item, ItemsControl parent, Style parentItemContainerStyle)
        {
            ContentItemsControl contentItemsControl = element as ContentItemsControl;
            if (contentItemsControl != null)
            {
                PrepareContentItemsControlContainer(contentItemsControl, item, parent, parentItemContainerStyle);
            }
        }

        #endregion

        #region Protected Members

        /// <summary>
        /// Called when the value of the <see cref="Content" /> property changes.
        /// </summary>
        /// <param name="oldContent">
        /// The old value of the <see cref="Content" /> property.
        /// </param>
        /// <param name="newContent">
        /// The new value of the <see cref="Content" /> property.
        /// </param>
        protected virtual void OnContentChanged(object oldContent, object newContent)
        {
        }

        /// <summary>
        /// Called when the value of the <see cref="ContentTemplate" /> property changes.
        /// </summary>
        /// <param name="oldContentTemplate">
        /// The old value of the <see cref="ContentTemplate" /> property.
        /// </param>
        /// <param name="newContentTemplate">
        /// The new value of the <see cref="ContentTemplate" /> property.
        /// </param>
        protected virtual void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate)
        {
        }

        #endregion

        #region Overrides

        /// <summary>
        /// Builds the visual tree for the <see cref="ContentItemsControl" />
        /// when a new template is applied.
        /// </summary>
        public override void OnApplyTemplate()
        {
            ItemsControlHelper.OnApplyTemplate();
            base.OnApplyTemplate();
        }

        /// <summary>
        /// Prepares the specified element to display the specified item.
        /// </summary>
        /// <param name="element">
        /// The container element used to display the specified item.
        /// </param>
        /// <param name="item">The content to display.</param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            ItemsControlHelper.PrepareContainerForItemOverride(element, ItemContainerStyle);
            PreparePrepareContentItemsControlContainerForItemOverride(element, item, this, ItemContainerStyle);

            base.PrepareContainerForItemOverride(element, item);
        }

        #endregion

        #region Private Members

        /// <summary>
        /// ContentProperty property changed handler.
        /// </summary>
        /// <param name="d">
        /// ContentItemsControl that changed its Content.
        /// </param>
        /// <param name="e">Event arguments.</param>
        private static void OnContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ContentItemsControl source = d as ContentItemsControl;
            source.OnContentChanged(e.OldValue, e.NewValue);
        }

        /// <summary>
        /// ContentTemplateProperty property changed handler.
        /// </summary>
        /// <param name="d">
        /// ContentItemsControl that changed its ContentTemplate.
        /// </param>
        /// <param name="e">Event arguments.</param>
        private static void OnContentTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ContentItemsControl source = d as ContentItemsControl;
            DataTemplate oldContentTemplate = e.OldValue as DataTemplate;
            DataTemplate newContentTemplate = e.NewValue as DataTemplate;
            source.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
        }

        /// <summary>
        /// ItemContainerStyleProperty property changed handler.
        /// </summary>
        /// <param name="d">
        /// ContentItemsControl that changed its ItemContainerStyle.
        /// </param>
        /// <param name="e">Event arguments.</param>
        private static void OnItemContainerStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ContentItemsControl source = d as ContentItemsControl;
            Style value = e.NewValue as Style;
            source.ItemsControlHelper.UpdateItemContainerStyle(value);
        }

        /// <summary>
        /// Prepare a PrepareContentItemsControlContainer container for an
        /// item.
        /// </summary>
        /// <param name="control">Container to prepare.</param>
        /// <param name="item">Item to be placed in the container.</param>
        /// <param name="parentItemsControl">The parent ItemsControl.</param>
        /// <param name="parentItemContainerStyle">
        /// The ItemContainerStyle for the parent ItemsControl.
        /// </param>
        private static void PrepareContentItemsControlContainer(ContentItemsControl control, object item, ItemsControl parentItemsControl, Style parentItemContainerStyle)
        {
            if (control != item)
            {
                // Copy the ItemsControl properties from parent to child
                DataTemplate parentItemTemplate = parentItemsControl.ItemTemplate;
                if (parentItemTemplate != null)
                {
                    control.SetValue(ItemTemplateProperty, parentItemTemplate);
                }
                if (parentItemContainerStyle != null && HasDefaultValue(control, ItemContainerStyleProperty))
                {
                    control.SetValue(ItemContainerStyleProperty, parentItemContainerStyle);
                }

                // Copy the Content properties from parent to child
                if (control.ContentIsItem || HasDefaultValue(control, ContentProperty))
                {
                    control.Content = item;
                    control.ContentIsItem = true;
                }
                if (parentItemTemplate != null)
                {
                    control.SetValue(ContentTemplateProperty, parentItemTemplate);
                }
                if (parentItemContainerStyle != null && control.Style == null)
                {
                    control.SetValue(StyleProperty, parentItemContainerStyle);
                }

                // Note: this is where we would apply the ContentTemplateSelector
                // (if implemented) or attempt to lookup the implicit template
                // for the type of the item if the ContentTemplate were null.

                // Setup a hierarchical template
                HierarchicalDataTemplate contentTemplate = parentItemTemplate as HierarchicalDataTemplate;
                if (contentTemplate != null)
                {
                    if (contentTemplate.ItemsSource != null && HasDefaultValue(control, ItemsSourceProperty))
                    {
                        control.SetBinding(
                            ItemsSourceProperty,
                            new Binding
                            {
                                Converter = contentTemplate.ItemsSource.Converter,
                                ConverterCulture = contentTemplate.ItemsSource.ConverterCulture,
                                ConverterParameter = contentTemplate.ItemsSource.ConverterParameter,
                                Mode = contentTemplate.ItemsSource.Mode,
                                NotifyOnValidationError = contentTemplate.ItemsSource.NotifyOnValidationError,
                                Path = contentTemplate.ItemsSource.Path,
                                Source = control.Content,
                                ValidatesOnExceptions = contentTemplate.ItemsSource.ValidatesOnExceptions
                            });
                    }
                    if (contentTemplate.ItemContainerStyle != null && control.ItemTemplate == parentItemTemplate)
                    {
                        control.ClearValue(ItemTemplateProperty);
                        if (contentTemplate.ItemTemplate != null)
                        {
                            control.ItemTemplate = contentTemplate.ItemTemplate;
                        }
                    }
                    if (contentTemplate.ItemContainerStyle != null && control.ItemContainerStyle == parentItemContainerStyle)
                    {
                        control.ClearValue(ItemContainerStyleProperty);
                        if (contentTemplate.ItemContainerStyle != null)
                        {
                            control.ItemContainerStyle = contentTemplate.ItemContainerStyle;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Check whether a control has the default value for a property.
        /// </summary>
        /// <param name="control">The control to check.</param>
        /// <param name="property">The property to check.</param>
        /// <returns>
        /// True if the property has the default value; false otherwise.
        /// </returns>
        private static bool HasDefaultValue(Control control, DependencyProperty property)
        {
            Debug.Assert(control != null, "control should not be null!");
            Debug.Assert(property != null, "property should not be null!");
            return control.ReadLocalValue(property) == DependencyProperty.UnsetValue;
        }

        #endregion
    }
}
